Skip to content

Rework & separate renderers#3539

Open
ACrazyTown wants to merge 79 commits intoHaxeFlixel:devfrom
ACrazyTown:seperate-renderer
Open

Rework & separate renderers#3539
ACrazyTown wants to merge 79 commits intoHaxeFlixel:devfrom
ACrazyTown:seperate-renderer

Conversation

@ACrazyTown
Copy link
Copy Markdown
Contributor

@ACrazyTown ACrazyTown commented Dec 23, 2025

Here we go, first step towards a renderer overhaul. Looks like a pretty big and scary change but 99% of this is just moving stuff around.

Shout out goes to Beeblerox & Austin East, a lot of this is based on the original renderer overhaul branch, I'm just porting bits and pieces to modern Flixel.

Changes

FlxRenderer

Adds a base FlxRenderer class, accessible via the global FlxG.renderer, which serves as the base for all rendering functionality. Renderer implementations extend it and implement the required methods.

The blitting and draw quads renderers have been ported to FlxBlitRenderer and FlxQuadRenderer, respectively.

Since the renderer is a global thing, and not per-camera anymore, it works slightly differently. Before any calls to drawing commands you need to call FlxG.renderer.begin(camera);. This will be done internally by Flixel during a sprite's draw phase, but it's something to keep in mind if you're doing something out of the ordinary!

FlxCameraView

Adds a base FlxCameraView class. Like with renderers, different implementations extend the base class and add onto it. Camera views mainly just hold per-camera rendering related objects, stuff like OpenFL sprites and whatnot.

To avoid breaking changes, you can get a typed reference to the camera view using the camera.viewBlit and camera.viewQuad shortcuts. Use this to reference stuff like camera.flashSprite or camera.canvas.

Other previously established stuff

Most rendering methods from FlxCamera have been deprecated. If you need to issue drawing commands manually, use the FlxG.renderer API instead.

I say most because some batch related methods (e.g. startQuadBatch()) have been left as-is. I plan to tackle these at a later point and in a different PR.


I'm opening this as a DRAFT, because:

  • This needs to be tested thoroughly to make sure I didn't accidentally break something
  • Would be nice to add docs to a bunch of stuff
  • I don't know what to do with a bunch of private internal variables in FlxCamera and alike. What's Flixel's way of handling this? Should I simply get rid of them, or deprecate them and make them point to where they were moved?

TODO:

  • flixel/addons/display/FlxShaderMaskCamera.hx:222 -- Field fill should be declared with overload since it was already declared as overload in superclass
  • Documentation
  • Figure out what to do with deprecated private functions/vars
  • Better function names?

@ACrazyTown
Copy link
Copy Markdown
Contributor Author

ACrazyTown commented Dec 24, 2025

I decided to deprecate all the internals and make them point to where they were moved, to avoid breaking anything.

Addons and UI will need to be updated to avoid warnings, I'll get to that in a bit. Addons specifically seems to have an issue because FlxShaderMaskCamera overrides camera.fill(), it'll need to be updated to also add overload extern, hopefully that's not a blocker

I think this is in a good enough of a state now for a review, so I'll undraft

@ACrazyTown ACrazyTown marked this pull request as ready for review December 24, 2025 17:02
@ACrazyTown
Copy link
Copy Markdown
Contributor Author

ACrazyTown commented Dec 26, 2025

Some more notes and thoughts. I'm currently playing around trying to implement an OpenGL renderer to really test the flexibility of this system.

Overloading methods in FlxCameraView won't work:

For stuff like FlxMaterial and FlxTrianglesData, the function signatures of the core rendering functions need to be changed. To avoid breaking changes, I was just going to do this with overloads, but that didn't work out because overloads need to be inlined and you can't override inline functions.

I got around this by deprecating drawPixels() and copyPixels() for draw() and copy() in FlxCameraView, but I couldn't do the same in FlxCamera because it inherits FlxBasic.draw(). I ended up overloading the camera's drawPixels() and copyPixels() to call the new methods in camera view. Pretty gross solution IMO but I can't think of a better way without a breaking change.

Unifying renderBlit with other renderers

I did some work related to this in 3cd97d9, but there's still a couple places where we have to do things differently depending on the renderer, notably drawPixels() and copyPixels():

flixel/flixel/FlxCamera.hx

Lines 743 to 744 in c32ab91

public function drawPixels(?frame:FlxFrame, ?pixels:BitmapData, matrix:FlxMatrix, ?transform:ColorTransform, ?blend:BlendMode, ?smoothing:Bool = false,
?shader:FlxShader):Void

When the blitting renderer is used, pixels will be used for the rendering, otherwise frame is used. I wonder if it's somehow possible to avoid this special behavior and just pass in one thing, without having to know what renderer is used. The user should just have to call camera.drawPixels(...); once, and the underlying implementation should take care of any special quirks.

Isolating direct access to renderer

Flixel shouldn't access any renderer implementations directly from common code, it should be done through the renderer abstraction. First thing that comes to mind is that we need to abstract way maxTextureSize

EDIT: This could be done via the RenderFrontEnd via some method like FlxG.render.getMaxTextureSize()

EDIT 2: This is no longer an issue with recent changes, see next comment

@ACrazyTown ACrazyTown marked this pull request as draft January 21, 2026 20:48
WIP; debug drawing is not functional yet and there's a bunch of temporary code that needs to be cleaned up
@ACrazyTown
Copy link
Copy Markdown
Contributor Author

Originally this was just a port of the renderer abstraction by Beeblerox that I ported over to modern Flixel. As I was playing around with it, I found that there were certain things I wasn't too fond of, specifically the fact that access to the renderer was only possible through cameras, so here goes an attempt at a V2.

FlxCameraView has been demoted, and no longer handles talking to the renderer. This is now done by the global FlxRenderer (Accessible via FlxG.renderer) and its implementations. FlxCameraView is still around, though it now mainly just stores the various objects needed per-camera for rendering (e.g. the flash sprites and whatever).

Considering that the renderer is now global, I've also integrated some of the helpers mentioned in #3527 directly into FlxRenderer.

I also wonder if it'd be worth deprecating the drawing methods in FlxCamera, and pointing users who need advanced control over the renderer to use FlxG.renderer instead.

@ACrazyTown
Copy link
Copy Markdown
Contributor Author

I think this is now in a reviewable state. No clue why CI is failing tho

@Geokureli
Copy link
Copy Markdown
Member

Gonna try a couple things, I'll let you know when I'm done, hopefully by sometime tomorrow

@Geokureli
Copy link
Copy Markdown
Member

Geokureli commented Mar 17, 2026

quoted from #3567

I'm a bit worried about tying the render methods to the camera view but I suppose this boils back to my previous sentence

The important distinction I wanna make is that, while we may move the rendering somewhere else, I do think it's important that to draw a sprite to a camera's view, you should call something akin to camera.view.drawThing(this) rather than something like FlxG.renderer.drawThingToView(camera.view, this), but viewBlit may end up calling privateRefToBlitRenderer.drawThing(this, sprite). I wanna focus on finding the best way to define these methods, before deciding exactly where they will end up.

FlxTexture, new methods that take bitmaps should take textures, instead

I have a draft (as discussed in #3540) for this typed up in a branch somewhere. It's implemented as a new class rather than an abstract so there is a bit of friction with implementing it with existing systems. Will need to revisit it and clean it up

The reason I make FlxVertexBuffer an abstract, is so that method args could be changed to take that type without breaking existing calls to it (as Graphics and FlxVertexBuffer can be implicitly casted to one another). Once devs migrate, FlxVertexBuffer will be a different type that will allow (or wrap) various underlying types. 1 option is to make it a class wrapper with derived types that use each specific underlying type. Another option is an abstract OneOfMany<T...> that calls the appropriate renderer

@ACrazyTown
Copy link
Copy Markdown
Contributor Author

The important distinction I wanna make is that, while we may move the rendering somewhere else, I do think it's important that to draw a sprite to a camera's view, you should call something akin to camera.view.drawThing(this) rather than something like FlxG.renderer.drawThingToView(camera.view, this), but viewBlit may end up calling privateRefToBlitRenderer.drawThing(this, sprite). I wanna focus on finding the best way to define these methods, before deciding exactly where they will end up.

Yeah this makes sense. To be clear, I'm not against putting the render methods in camera view, I'm moreso against tying them to cameras specifically, as this would lead us further away from separating cameras and rendering (#1073).

It'll be easier to reason with this once we have all the missing pieces, so my focus now is to implement FlxTexture, FlxRenderTexture and the GL renderer (so that we can actually use the render texture). I already have basic quads working, so hopefully it won't take too long to get most things up and running.

In the meantime, I'd appreciate any help on #3566, as I think that's going to be one of the toughest parts to figure out.

The reason I make FlxVertexBuffer an abstract, is so that method args could be changed to take that type without breaking existing calls to it (as Graphics and FlxVertexBuffer can be implicitly casted to one another). Once devs migrate, FlxVertexBuffer will be a different type that will allow (or wrap) various underlying types. 1 option is to make it a class wrapper with derived types that use each specific underlying type. Another option is an abstract OneOfMany<T...> that calls the appropriate renderer

I don't think I can make FlxTexture an abstract because it introduces some new fields, but also works slightly differently. If devs need the actual BitmapData they can either:

  • Use texture.handle to get the underlying representation. (Not recommended, if/when Flixel adopts other backends this will be changed to whatever type)
  • or use texture.getBitmap(), which returns a abstract FlxBitmap(BitmapData) and also undumps the texture if needed (Recommended)

Hooking it up is doable, it's just an extreme hassle because everything expects a BitmapData. I'll try to finish and PR it here soon, tho

Was causing crashes with the GL renderer as it doesn't use the FlxDrawItems at all. Seems like there's no functional difference between manually adding quads to a batch and just calling drawFrame() and having it done automatically.
@ACrazyTown
Copy link
Copy Markdown
Contributor Author

@Geokureli I've opened up a PR for FlxTexture: ACrazyTown#2

Comment on lines +197 to +198
public function drawTriangles(graphic:FlxGraphic, vertices:FlxVector2d<Float>, indices:FlxVector2d<Int>, uvtData:FlxVector2d<Float>, ?colors:FlxVector2d<Int>,
?position:FlxPoint, ?blend:BlendMode, repeat:Bool = false, smoothing:Bool = false, ?transform:ColorTransform, ?shader:FlxShader)
Copy link
Copy Markdown
Contributor Author

@ACrazyTown ACrazyTown Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Geokureli Is FlxVector2d meant to act as an abstraction over openfl's Vector, that would eventually become a new class, or is it just a helper? It makes sense for positions and uvs, which are treated as (x, y) pairs, but it doesn't make sense to use it for indices and colors which have a value per coordinate pair. (They're half the length of vertices)

addition takes precedence over bitwise ops and was causing invalid/inaccurate values

https://haxe.org/manual/expression-operators-precedence.html
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants